package com.yanceysong.snake;

import com.yanceysong.snake.database.SnakeDataDatabaseUtil;
import com.yanceysong.snake.image.ImageDataMapping;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Objects;
import java.util.Random;
import java.util.Scanner;

/**
 * @ClassName SnakeGamePanel
 * @Description
 * @date 2024/7/2 下午6:02
 * @Author yanceysong
 * @Version 1.0
 */
public class SnakeGamePanel extends JPanel implements KeyListener, ActionListener {

    public SnakeGamePanel() {
        init();
        this.setFocusable(true);//让这个组件获取到焦点
        this.addKeyListener(this);
        /*
       用于接收键盘事件（击键）的侦听器接口。
                使用组件的 addKeyListener 方法将从该类所创建的侦听器对象向该组件注册。按下、释放或键入键时生成键盘事件。
            然后调用侦听器对象中的相关方法并将该 KeyEvent 传递给它。
         */
        timer.start();//计时器打开
    }

    Timer timer = new Timer(160, this);//每160毫秒刷新一次，保证动画动起来
    int lenth;
    int foodx = 0;//设置食物的横坐标
    int foody = 0;//设置食物的纵坐标
    int x = 0;//设置食物的临时横坐标
    int y = 0;//设置食物的临时纵坐标
    boolean isfood = false;//设置当前是否有食物变量，默认为假
    boolean start = false;//设置当前游戏是否开始变量，默认为假
    boolean iskilled = false;
    String fx = "R";//R 向右 L向左 U向上 D向下
    int[] snakex = new int[999];
    int[] snakey = new int[999];
    Random Ran = new Random();//创建随机数对象，给食物的坐标生成随机数并且赋值

    public void init() {//初始化方法，当本类被构造的时候被执行一次
        fx = "R";
        lenth = 9;//设置蛇的输出长度
        snakex[0] = 200;
        snakey[0] = 100;//头部坐标
        snakex[1] = 200;
        snakey[1] = 100;
        snakex[2] = 179;
        snakey[2] = 100;//第一节坐标
        for (int i = 2; i < lenth; i++) {
            /*
            其他节坐标以此赋值，因为第一节身体坐标与头部一样，
            所以从第二节开始输出
             */
            snakex[i + 1] = snakex[i] - 19;
            snakey[i + 1] = snakey[i];
        }
    }

    Scanner sc = new Scanner(System.in);

    @Override
    protected void paintComponent(Graphics g) {//重写这个方法
        this.setFocusable(true);//让这个组件获取到焦点
        super.paintComponent(g);//清屏的作用，获取到父类已经创建好的Graphics g 画笔
        this.setBackground(Color.lightGray);//设置画板背景颜色
        g.fillRect(0, 0, 884, 681);//添加组件，绘制游戏区域
        switch (fx) {
            case "R":
                //判断方向是哪个，输出对应的方向图标
                ImageDataMapping.right.paintIcon(this, g, snakex[0], snakey[0]);
                break;
            case "L":
                ImageDataMapping.left.paintIcon(this, g, snakex[0], snakey[0]);
                break;
            case "U":
                ImageDataMapping.up.paintIcon(this, g, snakex[0], snakey[0]);
                break;
            case "D":
                ImageDataMapping.down.paintIcon(this, g, snakex[0], snakey[0]);
                break;
        }
        for (int i = 2; i < lenth; i++) {//循环输出多少个身体
            ImageDataMapping.body.paintIcon(this, g, snakex[i], snakey[i]);
        }
        ImageDataMapping.food.paintIcon(this, g, foodx, foody);//输出食物
        g.setColor(Color.WHITE);//设置画笔的颜色
        g.setFont(new Font("宋体", Font.BOLD, 20));//设置画笔的大小，字体，
        g.drawString("当前得分：" + (lenth - 9), 750, 50);//画笔输出对应的分数
        if (!start) {//游戏是否开始，如果没有开始利用画笔在画板内写一句话
            g.setFont(new Font("宋体", Font.BOLD, 40));
            g.drawString("按下空格键进行游戏！", 250, 300);
        }
        /*
        用循环不断检查蛇头坐标与身体坐标是否相等，如果相等，定时器终止，则提示游戏结束
         */
        for (int i = 3; i < lenth; i++) {
            if (snakey[0] == snakey[i] && snakex[0] == snakex[i]) {
                System.out.println("游戏结束！");
                g.setFont(new Font("宋体", Font.BOLD, 40));
                g.drawString("输入您的名字！按空格键重新开始游戏", 100, 300);
                timer.stop();//游戏画面暂停
                iskilled = true;//死亡 状态变为真
                System.out.print("请输入玩家： ");
                String s = sc.nextLine();
                if (!s.equals("查询"))
                    SnakeDataDatabaseUtil.insert(s, lenth - 9);
                else
                    SnakeDataDatabaseUtil.select();
                break;
            }
        }

        /**
         jdbc方法都是静态的，所以可以直接调用，知道调用jdbc类当中的方法
         */
        if(iskilled){
            System.out.print("请输入玩家： ");
            String s = sc.nextLine();
            if (!s.equals("查询"))
                SnakeDataDatabaseUtil.insert(s,lenth-9);
            else
                SnakeDataDatabaseUtil.select();
        }
    }

    @Override
    public void keyPressed(KeyEvent e) {//键盘按下，未释放操作
        int keycode = e.getKeyCode();//定义一个变量用来接收键盘的数据
        if (keycode == 32)  //如果按下空格键(数字32)，用来启动或者暂停
        {
            if (iskilled) {//如果死亡状态为真，初始化全部参数，进行下一次游戏
                init();
                timer.start();
            }
            iskilled = false;
            start = !start;
            repaint();//刷新界面
        }
        /*
        为了防止向左运动的时候，方向键给予一个向右的命令造成死亡，所以设置了在向一个方向运动的时候，
        相反方向的命令不能够被运行。
        方法是当键盘接收到一个方向指令以后，判断是不是当前方向与获取的指令是否相反，相反则不允许更改
        当前方向
         */
        else if (keycode == 38 && !Objects.equals(fx, "D")) {
            fx = "U";
        } else if (keycode == 40 && !Objects.equals(fx, "U")) {
            fx = "D";
        } else if (keycode == 37 && !Objects.equals(fx, "R")) {
            fx = "L";
        } else if (keycode == 39 && !Objects.equals(fx, "L")) {
            fx = "R";
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {//执行定时操作
        if (start) {//游戏开始以后，让小蛇动起来
            switch (fx) {
                case "R":
                    if (snakex[0] > 874) {//超出边界就把坐标变成初始值
                        snakex[0] = -15;
                    }
                    snakex[0] = snakex[0] + 20;
                    for (int i = lenth - 1; i > 0; i--) {//输出右移时候的身子
                        snakex[i] = snakex[i - 1];
                        snakey[i] = snakey[i - 1];
                    }
                    break;
                case "L":
                    if (snakex[0] < 15) {
                        snakex[0] = 874;
                    }
                    snakex[0] = snakex[0] - 20;

                    for (int i = lenth - 1; i > 0; i--) {//输出左移时候的身子
                        snakex[i] = snakex[i - 1];
                        snakey[i] = snakey[i - 1];
                    }
                    break;
                case "U":
                    snakey[0] = snakey[0] - 20;
                    if (snakey[0] < 0) {
                        snakey[0] = 680;
                    }
                    for (int i = lenth - 1; i > 0; i--) {//输出上移时候的身子
                        snakex[i] = snakex[i - 1];
                        snakey[i] = snakey[i - 1];
                    }
                    break;
                case "D":
                    snakey[0] = snakey[0] + 20;//每次行走的单位

                    if (snakey[0] > 678) {//判断边界值
                        snakey[0] = 1;
                    }
                    for (int i = lenth - 1; i > 0; i--) {//输出下移时候的身子
                        snakex[i] = snakex[i - 1];
                        snakey[i] = snakey[i - 1];
                    }
                    break;
            }
        }
           /*
        判断食物是否被吃,因为蛇的单位是每20个单位进行行走，如果判断头的坐标和食物坐标完全相等时不现实的，所以就利用蛇与食物的
        绝对值相差19（蛇每走一步是20单位）以下认为吃到了食物！
        也可以在1-20置随机数，得到的随机数乘以20，保证是20的倍数，这样就存在与小蛇坐标相等的情况
         */
        if (Math.abs(snakex[0] - foodx) < 20 && Math.abs(snakey[0] - foody) < 20) {
            isfood = !isfood;//把状态取反
            lenth++;//蛇的长度自加
        }
        if (!isfood) {//判断地图是否存在食物，如果没有，就会进行一次绘图，把食物绘图在地图上食物的坐标通过Random置随机种子完成
            x = Ran.nextInt(30);
            y = Ran.nextInt(30);
            foody = y * 20;
            foodx = x * 20;
            isfood = !isfood;
        }
        repaint();
        /*
        其次，那么为什么不应该直接调用paint而应该调用repaint呢？查了很多资料，有一个说的很简单，直接。
repaint()是重要概念，它是在图形线程后追加一段重绘操作，是安全的！是系统真正调用的重绘！所以如果你需要某个部件刷新一下界面，记得调用repaint()，
千万不要直接调用paint()！
Graphics是一个抽象类，其实现大都是平台相关的，所以不容易自己创建一个graphics实例。一般graphics的实例会由依照你所在的桌面环境给出。

简答的说， repaint方法先清空了画面的内容，然后间接调用了paint方法，paint方法调用了paintComponent方法，完成了画面的刷新。

所以只要在定时器里面调用了这个方法，就会实时输出当前的画面。
         */
        timer.start();//定时器开始
    }
    /*
    一下两个重写方法对于本项目没有太的用处，所以不再赘述
     */

    @Override
    public void keyReleased(KeyEvent e) {//释放某个键操作

    }

    @Override
    public void keyTyped(KeyEvent e) {//键盘按下弹起操作
    }
}
